package fait.ferm.datafx;

import fait.ferm.datafx.TableRow;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;



/**
 * A convenience class intended to make creating a TableView easier. In particular,
 * this class can create a TableView, as well as default TableColumn instances
 * which are preconfigured with {@link TableColumn#cellValueFactoryProperty() cell
 * value factories} that are able to extract the relevant data from a given
 * Class type.
 * 
 * <p>In addition to this, the TableViewFactory is able to refine the 
 * automatically generated TableColumns by reducing the columns added to the
 * TableView, as well as renaming and reordering.
 * 
 * <p>An example of using this API to create a TableView is shown below:
 * 
 * <pre>
 * <code>
 * TableView<Person> tableView = TableViewFactory.
 *     create(Person.class, personsList).
 *     selectColumns("First Name", "Last Name", "Telecommuter", "Employee Type", "Balance", "Earnings").
 *     renameColumn("Employee Type", "Type").
 *     renameColumn("Telecommuter", "Remote").
 *     buildTableView();
 * </code>
 * </pre>
 * 
 * @author Jonathan Giles
 */
public class TableViewFactory<S > {
    
    private TableViewFactory() { }
    
//    public static <S> TableView<S> create(Class<? extends S> dataType) {
//        List<TableColumn<S,?>> columns = createColumns(dataType);
//        
//        TableView<S> table = new TableView<S>();
//        table.getColumns().setAll(columns);
//        
//        return table;
//    }
//    
//    public static <S> TableViewFactory create(List<? extends S> items) {
//        return create(FXCollections.observableArrayList(items));
//    }
//    
//    public static <S> TableViewFactory create(Class<? extends S> dataType, List<S> items) {
//        return create(dataType, FXCollections.observableArrayList(items));
//    }
//    
//    public static <S> TableViewFactory create(ObservableList<? extends S> items) {
//        return create(null, FXCollections.observableArrayList(items));
//    }
    
    public static <S > TableViewFactory create(Class<? > dataType, 
			final ObservableList<S> items) {
        if (items == null) {
            throw new NullPointerException("items can not be null");
        }
        
        final TableView<S> table = new TableView<S>();
        table.setItems(items);
        table.setEditable(true);
        
        if (dataType == null && table.getItems().isEmpty()) {
            // we'll have to create the columns the first time the items list
            // changes, so let's hook in a listener
            InvalidationListener listener = new InvalidationListener() {
                @Override public void invalidated(Observable o) {
                    if (! table.getItems().isEmpty()) {
                        createColumns(table);
                        
                        // remove listener
                        items.removeListener(this);
                    }
                }
            };
            table.getItems().addListener(listener);
        } else {
            createColumns(table);
        }
        
        return TableViewFactory.configure(table);
    }
    
    private static <S > void createColumns(TableView<S> table) {
        Class actualDataType = table.getItems().get(0).getClass();
        if (actualDataType != null) {
            List<TableColumn<S,?>> columns = createColumns(actualDataType);
            table.getColumns().setAll(columns);
        }
    }
    
	private static class ColumnParam implements Comparable<ColumnParam> {
		public int orderNo;
		public String name;
		public String displayName;
		public Class<?> type;

		public ColumnParam(int orderNo, String name, String displayName, Class<?> type) {
			this.orderNo = orderNo;
			this.name = name;
			this.displayName = displayName;
			this.type = type;
		}

		@Override
		public int compareTo(ColumnParam o) {
			return this.orderNo-o.orderNo;
		}
		
	}
	
    private static <S > List<TableColumn<S ,?>> createColumns(Class<? > dataType) {
        List<TableColumn<S ,?>> columns = new ArrayList<TableColumn<S, ?>>();
            // TODO inspect the class, create columns for all public properties
            BeanInfo beanInfo = null;
		try {
			beanInfo = Introspector.getBeanInfo(dataType);
		} catch (IntrospectionException ex) {
			Logger.getLogger(TableViewFactory.class.getName()).log(Level.SEVERE, null, ex);
		}
            PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
			ArrayList<ColumnParam> ar = new ArrayList<>();

			for(Field f: dataType.getDeclaredFields()){
				System.out.println(""+ f.getName());
			}
			System.out.println("");
			for(PropertyDescriptor p: properties){
				System.out.println(""+p.getName());
			}
			System.out.println("");
			
            for (int i = 0; i < properties.length; i++) {
				try {
					PropertyDescriptor pd = properties[i];
					if ("class".equals(pd.getName())){ continue;}
					if (pd.getReadMethod() == null) { continue; }


					Field fd = dataType.getDeclaredField(pd.getName());
					TableRow tr = fd.getAnnotation(TableRow.class);
					if(tr!=null){
						ar.add(new ColumnParam(tr.orderNo(), pd.getName(), tr.displayName(), pd.getPropertyType()));
					}

				} catch (	NoSuchFieldException | SecurityException ex) {
					Logger.getLogger(TableViewFactory.class.getName()).log(Level.SEVERE, null, ex);
				}
			}	
			Collections.sort(ar);
			for(ColumnParam c: ar){
				TableColumn column = new TableColumn();
				column.setText(c.displayName);
				column.setCellValueFactory(new PropertyValueFactory(c.name));
				columns.add(column);
				
				// TODO set property name in the TableColumn properties map
				
				// install custom cell factory
				if (c.type.isEnum()) {
					Object[] enumConstants = c.type.getEnumConstants();
					column.setCellFactory(ChoiceBoxTableCell.forTableColumn(enumConstants));
				} else if (c.type == boolean.class) {
					column.setCellFactory(CheckBoxTableCell.forTableColumn(column));
				} else if (c.type == String.class) {
					column.setCellFactory(TextFieldTableCell.forTableColumn());
				}
			}
        return columns;
    }
    
    private static String makePrettyDisplayName(String name) {
        // split at each capital letter
        if (name == null || name.length() == 0) {
	    return name;
	}
	if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
			Character.isUpperCase(name.charAt(0))){
	    return name;
	}
        
	char chars[] = name.toCharArray();
        
        List<Character> charList = new ArrayList<Character>(chars.length);
        for (int i = 0; i < chars.length; i++) {
            char c0 = chars[i];
            char c1 = i > 0 ? chars[i - 1] : ' ';
            
            if (i == 0) {
                charList.add(Character.toUpperCase(c0));
                continue;
            }
            
            if (Character.isUpperCase(c0) && ! Character.isUpperCase(c1)) {
                // insert space
                charList.add(' ');
            }
            charList.add(c0);
        }
        
        chars = new char[charList.size()];
        for (int i = 0; i < charList.size(); i++) {
            chars[i] = charList.get(i);
        }
	return new String(chars);
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    private TableView<S> table;
    private ObservableList<TableColumn<S,?>> columns;
    
    private boolean columnSelectPerformed = false;
    private ObservableList<TableColumn<S,?>> finalColumns;
    
    public static <S  > TableViewFactory configure(TableView<S> table) {
        return new TableViewFactory<>(table);
    }
    
    public static void print(TableView table) {
        print(table.getColumns());
    }
    
    public static <S> void print(ObservableList<TableColumn<S,?>> columns) {
        System.out.println("Columns:");
        for (int i = 0; i < columns.size(); i++) {
            TableColumn tc = columns.get(i);
            System.out.println("    Text: " + tc.getText());
        }
    }
    
    private TableViewFactory(TableView<S> table/*, ObservableList<TableColumn> columns*/) {
        this.table = table;
        this.columns = table.getColumns(); //columns == null ? table.getColumns() : columns;
        this.finalColumns = FXCollections.observableArrayList();
        
        if (this.columns == null) {
            throw new NullPointerException("Columns can not be null");
        }
    }
    
    public TableViewFactory<S> selectColumns(String... names) {
        if (names == null || names.length == 0) return this;
        
        for (int i = 0; i < columns.size(); i++) {
            TableColumn tc = columns.get(i);
            
            for (int j = 0; j < names.length; j++) {
                String name = names[j];
                if (name == null) continue;
                if (name.equals(tc.getText())) {
                    finalColumns.add(tc);
                    columnSelectPerformed = true;
                }
            }
        }
        return this;
    }
    
    public TableViewFactory<S> renameColumn(String oldName, String newName) {
        if (oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty()) {
            return this;
        }
        for (int i = 0; i < columns.size(); i++) {
            TableColumn tc = columns.get(i);
            if (oldName.equals(tc.getText())) {
                tc.setText(newName);
                break;
            }
        }
        return this;
    }
    
    public ObservableList<TableColumn<S,?>> buildColumns() {
        return finalColumns;
    }
    
    public TableView<S> buildTableView() {
        if (table == null) {
            table = new TableView<S>();
        }
        
        if (columnSelectPerformed) {
            table.getColumns().setAll(finalColumns);
        } else if (! table.getColumns().equals(columns)) {
            table.getColumns().setAll(columns);
        }
        
        return table;
    }
    
//    public ObservableList<TableColumn<S,?>> buildAndSetInTableView() {
//        if (table == null) {
//            throw new IllegalStateException("Can not set columns in TableView, as TableView instance is null");
//        }
//        table.getColumns().setAll(finalColumns);
//        return finalColumns;
//    }
}
